<?php

namespace App\Services;

use App\Common\Libs\ArrayHelper;
use App\Common\Libs\Dispatch\Console;
use App\Common\Libs\IdBuilder;
use App\Common\Libs\PasswordHash;
use App\Common\Libs\WeChat;
use App\Console\Commands\Controller\LogLoginCommand;
use App\Consts\ExceptionCode;
use App\Consts\GlobalAPIReturnConst;
use App\Consts\GlobalAuthConst;
use App\Consts\GlobalCacheConst;
use App\Consts\GlobalConsoleConst;
use App\Consts\GlobalConst;
use App\Consts\GlobalCookieConst;
use App\Consts\GlobalLoginConst;
use App\Consts\GlobalSmsConst;
use App\Consts\GlobalUploadConst;
use App\Consts\GlobalUserConst;
use App\Exceptions\Error;
use App\Models\Cache\Cache;
use App\Models\Cache\CacheKey;
use App\Models\Field\UserField;
use App\Models\Select\UserSelect;

/**
 * @package App\Services
 * @see SiteService
 */
class SiteService extends BaseService
{
    /**
     * 手机号验证码登录
     * @param $params
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Support\Collection
     */
    public function phoneLogin($params)
    {
        // 验证码校验
        $verify = service()->SmsService->checkCodeByPhoneAndTemplate($params['code'], $params['phone'], GlobalSmsConst::SMS_TEMPLATE_LOGIN);
        if (!$verify) {
            return Error::handle(lang('Code disable'));
        }

        // 获取新令牌
        $accessToken = service()->LoginService->getAccessToken(GlobalLoginConst::LOGIN_PLATFORM_WEB);

        // 通过手机号获取用户
        $user = service()->UserService->getUserByPhone($params['phone']);

        /**
         * 用户存在：
         *  1. 检测用户状态
         *  2. 校验是否有令牌记录
         *      没有：（注册来源为其他端）则新增[login_web]表记录
         *      有：
         *          1.缓存中移除旧access_token
         *          2.刷新access_token和refresh_token
         * 否则：
         *  1. 新增[user]表记录
         *  2. 新增[login_web]表记录
         */
        if ($user) {
            // 用户状态异常
            if ((int)$user->status === GlobalUserConst::STATUS_DISABLE) {
                return Error::handle(lang('User status is disabled'));
            }

            // 用户昵称为空
            if (!$user->nickname) {
                service()->UserService->updateUserInfo([
                    'user_id' => $user->user_id,
                    'nickname' => service()->UserService->getRandNickname(),
                ]);
            }

            if (empty($user->loginWeb)) {
                service()->LoginService->createLogin([
                    'user_id' => $user->user_id,
                    'access_token' => $accessToken,
                    'refresh_token' => service()->LoginService->getRefreshToken(GlobalLoginConst::LOGIN_PLATFORM_WEB),
                    'expired_at' => time() + GlobalLoginConst::ACCESS_TOKEN_EXPIRED,
                    'refresh_expired_at' => time() + GlobalLoginConst::REFRESH_TOKEN_EXPIRED
                ]);
            } else {
                $login = $user->loginWeb;
                Cache::getInstance([
                    'platform' => GlobalLoginConst::LOGIN_PLATFORM_WEB_STR,
                    'token' => $login->access_token,
                ])->delete(CacheKey::USER_LOGIN_STATUS);

                service()->LoginService->resetTokenByLogin($login, [
                    'access_token' => $accessToken,
                    'refresh_token' => service()->LoginService->getRefreshToken(GlobalLoginConst::LOGIN_PLATFORM_WEB),
                    'expired_at' => time() + GlobalLoginConst::ACCESS_TOKEN_EXPIRED,
                    'refresh_expired_at' => time() + GlobalLoginConst::REFRESH_TOKEN_EXPIRED,
                ]);
            }

            $userId = $user->user_id;
        } else {
            $userId = IdBuilder::getUniqueID();
            $user = service()->UserService->createUserByPhone([
                'phone' => $params['phone'],
                'user_id' => $userId,
            ]);
            if (!$user) {
                return Error::handle('Save failed');
            }

            service()->LoginService->createLogin([
                'user_id' => $userId,
                'access_token' => $accessToken,
                'refresh_token' => service()->LoginService->getRefreshToken(GlobalLoginConst::LOGIN_PLATFORM_WEB),
                'expired_at' => time() + GlobalLoginConst::ACCESS_TOKEN_EXPIRED,
                'refresh_expired_at' => time() + GlobalLoginConst::REFRESH_TOKEN_EXPIRED,
            ]);
        }

        //  将登陆方式写入缓存
        Cache::getInstance([
            'user_id' => $userId
        ])->set(CacheKey::USER_LOGIN_METHOD, GlobalLoginConst::LOGIN_METHOD_TEL);

        // access_token写入缓存
        Cache::getInstance([
            'platform' => GlobalLoginConst::LOGIN_PLATFORM_WEB_STR,
            'token' => $accessToken,
        ])->set(CacheKey::USER_LOGIN_STATUS, $userId);

        // 记录登录日志
        Console::getInstance(LogLoginCommand::class)->push(service()->LogLoginService->getLoginSuccesLoadData([
            'user_id' => $user->user_id,
            'access_token' => $params['token'],
            'input_login_name' => $params['phone'],
            'login_type' => GlobalLoginConst::LOGIN_METHOD_TEL
        ]), GlobalConsoleConst::LOG_LOGIN_ADD);

        return collect($user->commonFormat(UserField::SELF_DETAIL))->merge([
            'access_token' => $accessToken,
            'identity' => GlobalUserConst::IDENTITY_USER,
            'login_method' => GlobalLoginConst::LOGIN_METHOD_TEL,
            'server_time' => time(),
            GlobalAPIReturnConst::__COOKIES => [GlobalCookieConst::TOKEN => $accessToken],
        ]);
    }

    /**
     * 手机号密码登录
     * @param $params
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Support\Collection
     */
    public function pwdLogin($params)
    {
        // 通过手机号获取用户
        $user = service()->UserService->getUserByPhone($params['phone']);
        if (!$user) {
            return Error::handle(lang('Phone user not exist'));
        }

        // 密码为空
        if (!$user->password_hash) {
            Console::getInstance(LogLoginCommand::class)->push(service()->LogLoginService->getLoginErrorLoadData([
                'user_id' => $user->user_id,
                'access_token' => $params['token'],
                'input_login_name' => $params['phone'],
                'code' => ExceptionCode::NOT_SET_PASSWORD,
                'login_type' => GlobalLoginConst::LOGIN_METHOD_TEL_PASSWORD
            ]), GlobalConsoleConst::LOG_LOGIN_ADD);
            return Error::handle(lang('Not set password'));
        }

        // 校验密码
        $validRes = PasswordHash::validatePassword($params['password'], $user->password_hash);
        if (!$validRes) {
            Console::getInstance(LogLoginCommand::class)->push(service()->LogLoginService->getLoginErrorLoadData([
                'user_id' => $user->user_id,
                'access_token' => $params['token'],
                'input_login_name' => $params['phone'],
                'code' => ExceptionCode::PASSWORD_ERROR,
                'login_type' => GlobalLoginConst::LOGIN_METHOD_TEL_PASSWORD
            ]), GlobalConsoleConst::LOG_LOGIN_ADD);
            return Error::handle(lang('Password Error'));
        }

        // 获取新令牌
        $accessToken = service()->LoginService->getAccessToken(GlobalLoginConst::LOGIN_PLATFORM_WEB);

        // 校验是否有令牌，没有（注册来源为其他端）则新增[login_web]表记录
        if (empty($user->loginWeb)) {
            service()->LoginService->createLogin([
                'user_id' => $user->user_id,
                'access_token' => $accessToken,
                'refresh_token' => service()->LoginService->getRefreshToken(GlobalLoginConst::LOGIN_PLATFORM_WEB),
                'expired_at' => time() + GlobalLoginConst::ACCESS_TOKEN_EXPIRED,
                'refresh_expired_at' => time() + GlobalLoginConst::REFRESH_TOKEN_EXPIRED
            ]);
        } else {
            $login = $user->loginWeb;
            Cache::getInstance([
                'platform' => GlobalLoginConst::LOGIN_PLATFORM_WEB_STR,
                'token' => $login->access_token,
            ])->delete(CacheKey::USER_LOGIN_STATUS);

            // 刷新access_token和refresh_token
            service()->LoginService->resetTokenByLogin($login, [
                'access_token' => $accessToken,
                'refresh_token' => service()->LoginService->getRefreshToken(GlobalLoginConst::LOGIN_PLATFORM_WEB),
                'expired_at' => time() + GlobalLoginConst::ACCESS_TOKEN_EXPIRED,
                'refresh_expired_at' => time() + GlobalLoginConst::REFRESH_TOKEN_EXPIRED,
            ]);
        }

        //  将登陆方式写入缓存
        Cache::getInstance([
            'user_id' => $user->user_id,
        ])->set(CacheKey::USER_LOGIN_METHOD, GlobalLoginConst::LOGIN_METHOD_TEL_PASSWORD);

        // access_token写入缓存
        Cache::getInstance([
            'platform' => GlobalLoginConst::LOGIN_PLATFORM_WEB_STR,
            'token' => $accessToken,
        ])->set(CacheKey::USER_LOGIN_STATUS, true);

        // 记录登录日志
        Console::getInstance(LogLoginCommand::class)->push(service()->LogLoginService->getLoginSuccesLoadData([
            'user_id' => $user->user_id,
            'access_token' => $params['token'],
            'input_login_name' => $params['phone'],
            'login_type' => GlobalLoginConst::LOGIN_METHOD_TEL_PASSWORD
        ]), GlobalConsoleConst::LOG_LOGIN_ADD);

        return collect($user->commonFormat(UserSelect::SELF_DETAIL))->merge([
            'access_token' => $accessToken,
            'identity' => GlobalUserConst::IDENTITY_USER,
            'login_method' => GlobalLoginConst::LOGIN_METHOD_TEL_PASSWORD,
            'server_time' => time(),
            GlobalAPIReturnConst::__COOKIES => [GlobalCookieConst::TOKEN => $accessToken],
        ]);
    }

    public function bindPhoneAfterThirdOAuth($params)
    {
        // 从缓存中获取授权用户（等于游客）信息
        $thirdTourist = Cache::getInstance([
            'token' => $params['token'],
        ])->hGetAll(CacheKey::OAUTH_TOURIST);
        if (!$thirdTourist) {
            return Error::handle(lang('Third oauth already expired'), ExceptionCode::LOGIN_FAIL);
        }

        $thirdTourist = ArrayHelper::toArray($thirdTourist);

        // 验证码校验
        $verify = service()->SmsService->checkCodeByPhoneAndTemplate($params['code'], $params['phone'], GlobalSmsConst::SMS_TEMPLATE_BIND);
        if (!$verify) {
            return Error::handle(lang('Code disable'));
        }

        $phoneUser = service()->UserService->getUserByPhone($params['phone']);
        // 判断手机号用户是否存在，存在，且绑定了手机号返回错误
        if ($phoneUser) {
            if (!empty($phoneUser->wechat)) {
                return Error::handle(lang('This phone already have user'));
            }

            // 手机号用户存在，且没有绑定微信
            // 用户再其他平台登录，[login_web]表可能没有令牌记录
            $accessToken = service()->LoginService->getAccessToken(GlobalConst::LOGIN_PLATFORM_WEB);
            if (empty($phoneUser->loginWeb)) {
                service()->LoginService->createLogin([
                    'user_id' => $phoneUser->user_id,
                    'access_token' => $accessToken,
                    'refresh_token' => service()->LoginService->getRefreshToken(GlobalLoginConst::LOGIN_PLATFORM_WEB),
                    'expired_at' => time() + GlobalLoginConst::ACCESS_TOKEN_EXPIRED,
                    'refresh_expired_at' => time() + GlobalLoginConst::REFRESH_TOKEN_EXPIRED
                ]);
            } else {
                service()->LoginService->resetTokenByLogin($phoneUser->loginWeb, [
                    'access_token' => $accessToken,
                    'refresh_token' => service()->LoginService->getRefreshToken(GlobalLoginConst::LOGIN_PLATFORM_WEB),
                    'expired_at' => time() + GlobalLoginConst::ACCESS_TOKEN_EXPIRED,
                    'refresh_expired_at' => time() + GlobalLoginConst::REFRESH_TOKEN_EXPIRED,
                ]);
            }

            // 新增授权信息
            $authData = $thirdTourist['auth'];
            $authData['user_id'] = $phoneUser->user_id;
            service()->AuthWxService->createAuth($authData);

            //  将登陆方式写入缓存
            Cache::getInstance([
                'user_id' => $phoneUser->user_id,
            ])->set(CacheKey::USER_LOGIN_METHOD, GlobalLoginConst::LOGIN_METHOD_WECHAT);

            // access_token写入缓存
            Cache::getInstance([
                'platform' => GlobalLoginConst::LOGIN_PLATFORM_WEB_STR,
                'token' => $accessToken,
            ])->set(CacheKey::USER_LOGIN_STATUS, true);

            // 清除游客授权信息
            Cache::getInstance([
                'token' => $accessToken,
            ])->delete(CacheKey::OAUTH_TOURIST);

            return [
                GlobalAPIReturnConst::__COOKIES => [GlobalCookieConst::TOKEN => $accessToken],
            ];
        }


        $thirdTourist['user_id'] = $thirdTourist['id'];

        // 保存用户信息
        service()->UserService->createUserByOauthWx([
            'user_id' => $thirdTourist['id'],
            'phone' => $params['phone'],
            'gender' => $thirdTourist['auth']['gender'],
            'nickname' => $thirdTourist['auth']['nickname'],
            'avatar' => $thirdTourist['auth']['local_avatar'],
            'last_login_at' => time(),
        ]);

        // 保存授权信息
        service()->AuthWxService->createAuth(ArrayHelper::merge($thirdTourist['auth'], [
            'user_id' => $thirdTourist['id']
        ]));

        $accessToken = service()->LoginService->getAccessToken(GlobalConst::LOGIN_PLATFORM_WEB);

        // 新增登录令牌信息
        service()->LoginService->createLogin([
            'user_id' => $thirdTourist['id'],
            'access_token' => $accessToken,
            'refresh_token' => service()->LoginService->getRefreshToken(GlobalLoginConst::LOGIN_PLATFORM_WEB),
            'expired_at' => time() + GlobalLoginConst::ACCESS_TOKEN_EXPIRED,
            'refresh_expired_at' => time() + GlobalLoginConst::REFRESH_TOKEN_EXPIRED
        ]);

        // 记录登录日志
        Console::getInstance(LogLoginCommand::class)->push(service()->LogLoginService->getLoginSuccesLoadData([
            'user_id' => $thirdTourist['id'],
            'access_token' => $params['token'],
            'input_login_name' => $params['phone'],
            'login_type' => GlobalLoginConst::LOGIN_METHOD_WECHAT
        ]), GlobalConsoleConst::LOG_LOGIN_ADD);

        //  将登陆方式写入缓存
        Cache::getInstance([
            'user_id' => $thirdTourist['id'],
        ])->set(CacheKey::USER_LOGIN_METHOD, GlobalLoginConst::LOGIN_METHOD_WECHAT);

        // access_token写入缓存
        Cache::getInstance([
            'platform' => GlobalLoginConst::LOGIN_PLATFORM_WEB_STR,
            'token' => $accessToken,
        ])->set(CacheKey::USER_LOGIN_STATUS, true);

        return [
            GlobalAPIReturnConst::__COOKIES => [GlobalCookieConst::TOKEN => $accessToken],
        ];
    }

    /**
     * 微信授权/绑定回调
     * @param $code
     * @param $state
     * @return string[]
     */
    public function wxCallback($code, $state)
    {
        // 解析oldState获取newState和state数组
        $parseState = service()->OAuthService->parseState($state);

        // 如果state中包含where参数则跳转
        $redirectUrl = $this->haveWhereRedirect($code, $state, $parseState['newState']);
        if ($redirectUrl) {
            return [GlobalAPIReturnConst::__REDIRECT_URL => $redirectUrl];
        }

        // 获取微信用户授权信息
        $userInfo = WeChat::getInstance()->getUserAuthInfoByCode($code);
        if (!$userInfo) {
            return $this->toUrl(env('WEBSITE_HOST'));
        }
        // state参数中不包含user_id说明是授权登录
        if (empty($parseState['stateParams']['user_id'])) {
            //获取游客的access_token
            $touristAccessToken = Cache::getInstance([
                'tourist_id' => $parseState['stateParams']['tourist_id']
            ])->get(CacheKey::TOURIST_TOKEN_SNAPSHOT);

            $user = service()->AuthWxService->getUserInfoByUnionid($userInfo['unionid']);
            // 根据unionid没有找到对用的用户，说明是新用户，以游客身份返回，并将授权信息入缓存
            if (!$user) {
                $tourist = service()->LoginService->getTourist();
                $tourist['id'] = $parseState['stateParams']['tourist_id'];
                $tourist['auth'] = [
                    'openid' => $userInfo['openid'],
                    'nickname' => $userInfo['nickname'],
                    'unionid' => $userInfo['unionid'],
                    'avatar' => $userInfo['headimgurl'],
                    'gender' => $userInfo['sex'],
                    'local_avatar' => \App\Common\Libs\Ali\Oss::getInstance(GlobalUploadConst::USER_AVATAR_PATH)
                        ->upload($userInfo['headimgurl'], $tourist['id']),
                ];
                $tourist['login_method'] = GlobalLoginConst::LOGIN_METHOD_WECHAT;

                // 游客信息写入缓存
                Cache::getInstance([
                    'token' => $touristAccessToken,
                ])->hset(
                    CacheKey::OAUTH_TOURIST,
                    $tourist,
                    GlobalCacheConst::EXPIRED_TWO_HOURS
                );

                // 登录方式写入缓存
                Cache::getInstance([
                    'user_id' => $parseState['stateParams']['tourist_id'],
                ])->set(CacheKey::USER_LOGIN_METHOD, GlobalLoginConst::LOGIN_METHOD_WECHAT, GlobalCacheConst::EXPIRED_TWO_HOURS);
            } else {
                // 异步清除旧access_token、写登录日志
                Cache::getInstance([
                    'platform' => GlobalLoginConst::LOGIN_PLATFORM_WEB_STR,
                    'token' => $user->loginWeb->access_token,
                ])->delete(CacheKey::USER_LOGIN_STATUS);
                $user->loginWeb->update(['access_token' => $touristAccessToken]);

                Cache::getInstance([
                    'user_id' => $user->user_id,
                ])->set(
                    CacheKey::USER_LOGIN_METHOD,
                    GlobalLoginConst::LOGIN_METHOD_WECHAT,
                    GlobalCacheConst::EXPIRED_TWO_HOURS
                );

                // 记录登录日志
                Console::getInstance(LogLoginCommand::class)->push(service()->LogLoginService->getLoginSuccesLoadData([
                    'user_id' => $user->user_id,
                    'access_token' => $touristAccessToken,
                    'input_login_name' => $userInfo['unionid'],
                    'login_type' => GlobalLoginConst::LOGIN_METHOD_WECHAT
                ]), GlobalConsoleConst::LOG_LOGIN_ADD);

                Cache::getInstance([
                    'platform' => GlobalLoginConst::LOGIN_PLATFORM_WEB_STR,
                    'token' => $touristAccessToken,
                ])->set(CacheKey::USER_LOGIN_STATUS, true);
            }

            return $this->toUrl($parseState['stateParams']['url'], [
                'login_method' => GlobalLoginConst::LOGIN_METHOD_WECHAT,
                'is_fail' => 0,
                GlobalAPIReturnConst::__COOKIES => [GlobalCookieConst::TOKEN => $touristAccessToken]
            ]);
        }

        // 绑定
        $unionidUser = service()->AuthWxService->getUserInfoByUnionid($userInfo['unionid']);

        // 微信已经绑定用户
        if ($unionidUser) {
            return $this->toUrl($parseState['stateParams']['url'], [
                'type' => GlobalAuthConst::AUTH_WECHAT,
                'platname' => $userInfo['nickname'],
                'nickname' => $unionidUser->nickname,
                'is_fail' => 1,
            ]);
        }

        // 新增授权记录
        $params = [
            'user_id' => $parseState['stateParams']['user_id'],
            'openid' => $userInfo['openid'],
            'unionid' => $userInfo['unionid'],
            'nickname' => $userInfo['nickname'],
            'gender' => $userInfo['sex'],
            'avatar' => $userInfo['headimgurl'],
        ];
        service()->AuthWxService->createAuth($params);

        return $this->toUrl($parseState['stateParams']['url'], [
            'is_fail' => 0,
            'platname' => $userInfo['nickname'],
        ]);
    }

    private function haveWhereRedirect($code, $oldState, $newState)
    {
        if (strpos($oldState, 'where') !== false) {
            if (strpos($oldState, 'where=t') !== false) {
                return 'https://tapi.tingjunapp.com/api/site/wx-callback?code=' . $code . '&state=' . $newState;
            }
            if (strpos($oldState, 'where=d') !== false) {
                return 'https://dapi.tingjunapp.com/api/site/wx-callback?code=' . $code . '&state=' . $newState;
            }
        }

        return false;
    }

    private function toUrl($url, $otherParams = null)
    {
        $url = urldecode($url);

        $redirectInfo = parse_url($url);
        $port = empty($redirectInfo['port']) ? '' : ':' . $redirectInfo['port'];
        $redirectUrl = $redirectInfo['scheme'] . '://' . $redirectInfo['host'] . $port . '/blank';
        // 莫名其妙的bug，url中包含amp;amp;
        $str = '';
        if ($otherParams) {
            foreach ($otherParams as $key => $value) {
                if (!is_array($value)) {
                    if (in_array($key, ['code', 'state', 'replace', 'where'])) {
                        continue;
                    }
                    if (strpos($key, 'amp;amp;') !== false) {
                        $key = str_replace('amp;amp;', '', $key);
                    }
                    $str .= '&' . $key . '=' . $value;
                }
            }
        }

        if ($str) {
            return [GlobalAPIReturnConst::__REDIRECT_URL => $redirectUrl . '?' . substr($str, 1)];
        }

        return [GlobalAPIReturnConst::__REDIRECT_URL => $redirectUrl];
    }
}
